/** Copyright (C) 2012
* Arindam Nath (strider2023@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.touchmenotapps.widget.radialmenu.semicircularmenu;
import java.util.HashMap;
import com.touchmenotapps.widget.radialmenu.RadialMenuColors;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* This is the core class that handles the widget display and user interaction.
* TODO At times the arc area bound check fails. Gotta check that.
* @author Arindam Nath (strider2023@gmail.com)
*/
public class SemiCircularRadialMenu extends View {
//Static Access Variables
public static final int VERTICAL_RIGHT = 0;
public static final int VERTICAL_LEFT = 1;
public static final int HORIZONTAL_TOP = 2;
public static final int HORIZONTAL_BOTTOM = 3;
//Private non-shared variables
private boolean isMenuVisible = false;
private boolean isMenuTogglePressed = false;
private boolean isMenuItemPressed = false;
private String mPressedMenuItemID = null;
private int mDiameter = 0;
private float mRadius = 0.0f;
private int mStartAngle = 0;
private RectF mMenuRect;
private RectF mMenuCenterButtonRect;
private Paint mRadialMenuPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Point mViewAnchorPoints;
private HashMap<String, SemiCircularRadialMenuItem> mMenuItems = new HashMap<String, SemiCircularRadialMenuItem>();
//Variables that can be user defined
private float mShadowRadius = 5 * getResources().getDisplayMetrics().density;
private boolean isShowMenuText = false;
private int mOrientation = HORIZONTAL_BOTTOM;
private int centerRadialColor = Color.WHITE;
private int mShadowColor = Color.GRAY;
private String openMenuText = "Open";
private String closeMenuText = "Close";
private String centerMenuText = openMenuText; //Not to be set using setter method
private int mToggleMenuTextColor = Color.DKGRAY;
private float textSize = 12 * getResources().getDisplayMetrics().density;
private int mOpenButtonScaleFactor = 3;
public SemiCircularRadialMenu(Context context) {
super(context);
init();
}
public SemiCircularRadialMenu(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SemiCircularRadialMenu(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mRadialMenuPaint.setTextSize(textSize);
mRadialMenuPaint.setColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRadialMenuPaint.setShadowLayer(mShadowRadius, 0.0f, 0.0f, mShadowColor);
//Draw the menu if the menu is to be displayed.
if(isMenuVisible) {
canvas.drawArc(mMenuRect, mStartAngle, 180, true, mRadialMenuPaint);
//See if there is any item in the collection
if(mMenuItems.size() > 0) {
float mStart = mStartAngle;
//Get the sweep angles based on the number of menu items
float mSweep = 180/mMenuItems.size();
for(SemiCircularRadialMenuItem item : mMenuItems.values()) {
mRadialMenuPaint.setColor(item.getBackgroundColor());
item.setMenuPath(mMenuCenterButtonRect, mMenuRect, mStart, mSweep, mRadius, mViewAnchorPoints);
canvas.drawPath(item.getMenuPath(), mRadialMenuPaint);
if(isShowMenuText) {
mRadialMenuPaint.setShadowLayer(mShadowRadius, 0.0f, 0.0f, Color.TRANSPARENT);
mRadialMenuPaint.setColor(item.getTextColor());
canvas.drawTextOnPath(item.getText(), item.getMenuPath(), 5, textSize, mRadialMenuPaint);
mRadialMenuPaint.setShadowLayer(mShadowRadius, 0.0f, 0.0f, mShadowColor);
}
item.getIcon().draw(canvas);
mStart += mSweep;
}
mRadialMenuPaint.setStyle(Style.FILL);
}
}
//Draw the center menu toggle piece
mRadialMenuPaint.setColor(centerRadialColor);
canvas.drawArc(mMenuCenterButtonRect, mStartAngle, 180, true, mRadialMenuPaint);
mRadialMenuPaint.setShadowLayer(mShadowRadius, 0.0f, 0.0f, Color.TRANSPARENT);
//Draw the center text
drawCenterText(canvas, mRadialMenuPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(mMenuCenterButtonRect.contains(x, y)) {
centerRadialColor = RadialMenuColors.HOLO_LIGHT_BLUE;
isMenuTogglePressed = true;
invalidate();
} else if(isMenuVisible) {
if(mMenuItems.size() > 0) {
for(SemiCircularRadialMenuItem item : mMenuItems.values()) {
if(mMenuRect.contains((int) x, (int) y))
if(item.getBounds().contains((int) x, (int) y)) {
isMenuItemPressed = true;
mPressedMenuItemID = item.getMenuID();
break;
}
}
mMenuItems.get(mPressedMenuItemID)
.setBackgroundColor(mMenuItems.get(mPressedMenuItemID).getMenuSelectedColor());
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
if(isMenuTogglePressed) {
centerRadialColor = Color.WHITE;
if(isMenuVisible) {
isMenuVisible = false;
centerMenuText = openMenuText;
} else {
isMenuVisible = true;
centerMenuText = closeMenuText;
}
isMenuTogglePressed = false;
invalidate();
}
if(isMenuItemPressed) {
if(mMenuItems.get(mPressedMenuItemID).getCallback() != null) {
mMenuItems.get(mPressedMenuItemID).getCallback().onMenuItemPressed();
}
mMenuItems.get(mPressedMenuItemID)
.setBackgroundColor(mMenuItems.get(mPressedMenuItemID).getMenuNormalColor());
isMenuItemPressed = false;
invalidate();
}
break;
}
return true;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//Determine the diameter and the radius based on device orientation
if(w > h) {
mDiameter = h;
mRadius = mDiameter/2 - (getPaddingTop() + getPaddingBottom());
} else {
mDiameter = w;
mRadius = mDiameter/2 - (getPaddingLeft() + getPaddingRight());
}
//Init the draw arc Rect object
mMenuRect = getRadialMenuRect(false);
mMenuCenterButtonRect = getRadialMenuRect(true);
}
/**
* Draw the toggle menu button text.
* @param canvas
* @param paint
*/
private void drawCenterText(Canvas canvas, Paint paint) {
paint.setColor(mToggleMenuTextColor);
switch(mOrientation) {
case VERTICAL_RIGHT:
canvas.drawText(centerMenuText, getWidth() - paint.measureText(centerMenuText), getHeight()/2, paint);
break;
case VERTICAL_LEFT:
canvas.drawText(centerMenuText, 2, getHeight()/2, paint);
break;
case HORIZONTAL_TOP:
canvas.drawText(centerMenuText, (getWidth()/2) - (paint.measureText(centerMenuText)/2), textSize, paint);
break;
case HORIZONTAL_BOTTOM:
canvas.drawText(centerMenuText, (getWidth()/2) - (paint.measureText(centerMenuText)/2), getHeight() - (textSize), paint);
break;
}
}
/**
* Get the arc drawing rects
* @param isCenterButton
* @return
*/
private RectF getRadialMenuRect(boolean isCenterButton) {
int left, right, top, bottom;
left = right = top = bottom= 0;
switch(mOrientation) {
case VERTICAL_RIGHT:
if(isCenterButton) {
left = getWidth() - (int) (mRadius/mOpenButtonScaleFactor);
right = getWidth() + (int) (mRadius/mOpenButtonScaleFactor);
top = (getHeight()/2) - (int) (mRadius/mOpenButtonScaleFactor);
bottom = (getHeight()/2) + (int) (mRadius/mOpenButtonScaleFactor);
} else {
left = getWidth() - (int) mRadius;
right = getWidth() + (int) mRadius;
top = (getHeight()/2) - (int) mRadius;
bottom = (getHeight()/2) + (int) mRadius;
}
mStartAngle = 90;
mViewAnchorPoints = new Point(getWidth(), getHeight()/2);
break;
case VERTICAL_LEFT:
if(isCenterButton) {
left = -(int) (mRadius/mOpenButtonScaleFactor);
right = (int) (mRadius/mOpenButtonScaleFactor);
top = (getHeight()/2) - (int) (mRadius/mOpenButtonScaleFactor);
bottom = (getHeight()/2) + (int) (mRadius/mOpenButtonScaleFactor);
} else {
left = -(int) mRadius;
right = (int) mRadius;
top = (getHeight()/2) - (int) mRadius;
bottom = (getHeight()/2) + (int) mRadius;
}
mStartAngle = 270;
mViewAnchorPoints = new Point(0, getHeight()/2);
break;
case HORIZONTAL_TOP:
if(isCenterButton) {
left = (getWidth()/2) - (int) (mRadius/mOpenButtonScaleFactor);
right = (getWidth()/2) + (int) (mRadius/mOpenButtonScaleFactor);
top = -(int) (mRadius/mOpenButtonScaleFactor);
bottom = (int) (mRadius/mOpenButtonScaleFactor);
} else {
left = (getWidth()/2) - (int) mRadius;
right = (getWidth()/2) + (int) mRadius;
top = -(int) mRadius;
bottom = (int) mRadius;
}
mStartAngle = 0;
mViewAnchorPoints = new Point(getWidth()/2, 0);
break;
case HORIZONTAL_BOTTOM:
if(isCenterButton) {
left = (getWidth()/2) - (int) (mRadius/mOpenButtonScaleFactor);
right = (getWidth()/2) + (int) (mRadius/mOpenButtonScaleFactor);
top = getHeight() - (int) (mRadius/mOpenButtonScaleFactor);
bottom = getHeight() + (int) (mRadius/mOpenButtonScaleFactor);
} else {
left = (getWidth()/2) - (int) mRadius;
right = (getWidth()/2) + (int) mRadius;
top = getHeight() - (int) mRadius;
bottom = getHeight() + (int) mRadius;
}
mStartAngle = 180;
mViewAnchorPoints = new Point(getWidth()/2, getHeight());
break;
}
Rect rect = new Rect(left, top, right, bottom);
Log.i(VIEW_LOG_TAG, " Top " + top + " Bottom " + bottom + " Left " + left + " Right " + right);
return new RectF(rect);
}
/********************************************************************************************
* Getter and setter methods
********************************************************************************************/
/**
* Set the orientation the semi-circular radial menu.
* There are four possible orientations only
* VERTICAL_RIGHT , VERTICAL_LEFT , HORIZONTAL_TOP,
* HORIZONTAL_BOTTOM
* @param orientation
*/
public void setOrientation(int orientation) {
mOrientation = orientation;
mMenuRect = getRadialMenuRect(false);
mMenuCenterButtonRect = getRadialMenuRect(true);
invalidate();
}
/**
* Add a menu item with it's identifier tag
* @param idTag - Menu item identifier id
* @param mMenuItem - RadialMenuItem object
*/
public void addMenuItem(String idTag, SemiCircularRadialMenuItem mMenuItem) {
mMenuItems.put(idTag, mMenuItem);
invalidate();
}
/**
* Remove a menu item with it's identifier tag
* @param idTag - Menu item identifier id
*/
public void removeMenuItemById(String idTag) {
mMenuItems.remove(idTag);
invalidate();
}
/**
* Remove a all menu items
*/
public void removeAllMenuItems() {
mMenuItems.clear();
invalidate();
}
/**
* Dismiss an open menu.
*/
public void dismissMenu() {
isMenuVisible = false;
centerMenuText = openMenuText;
invalidate();
}
/**
* @return the mShadowRadius
*/
public float getShadowRadius() {
return mShadowRadius;
}
/**
* @param mShadowRadius the mShadowRadius to set
*/
public void setShadowRadius(int mShadowRadius) {
this.mShadowRadius = mShadowRadius * getResources().getDisplayMetrics().density;
invalidate();
}
/**
* @return the isShowMenuText
*/
public boolean isShowMenuText() {
return isShowMenuText;
}
/**
* @param isShowMenuText the isShowMenuText to set
*/
public void setShowMenuText(boolean isShowMenuText) {
this.isShowMenuText = isShowMenuText;
invalidate();
}
/**
* @return the mOrientation
*/
public int getOrientation() {
return mOrientation;
}
/**
* @return the centerRadialColor
*/
public int getCenterRadialColor() {
return centerRadialColor;
}
/**
* @param centerRadialColor the centerRadialColor to set
*/
public void setCenterRadialColor(int centerRadialColor) {
this.centerRadialColor = centerRadialColor;
invalidate();
}
/**
* @return the mShadowColor
*/
public int getShadowColor() {
return mShadowColor;
}
/**
* @param mShadowColor the mShadowColor to set
*/
public void setShadowColor(int mShadowColor) {
this.mShadowColor = mShadowColor;
invalidate();
}
/**
* @return the openMenuText
*/
public String getOpenMenuText() {
return openMenuText;
}
/**
* @param openMenuText the openMenuText to set
*/
public void setOpenMenuText(String openMenuText) {
this.openMenuText = openMenuText;
if(!isMenuTogglePressed)
centerMenuText = openMenuText;
invalidate();
}
/**
* @return the closeMenuText
*/
public String getCloseMenuText() {
return closeMenuText;
}
/**
* @param closeMenuText the closeMenuText to set
*/
public void setCloseMenuText(String closeMenuText) {
this.closeMenuText = closeMenuText;
if(isMenuTogglePressed)
centerMenuText = closeMenuText;
invalidate();
}
/**
* @return the mToggleMenuTextColor
*/
public int getToggleMenuTextColor() {
return mToggleMenuTextColor;
}
/**
* @param mToggleMenuTextColor the mToggleMenuTextColor to set
*/
public void setToggleMenuTextColor(int mToggleMenuTextColor) {
this.mToggleMenuTextColor = mToggleMenuTextColor;
invalidate();
}
/**
* @return the textSize
*/
public float getTextSize() {
return textSize;
}
/**
* @param textSize the textSize to set
*/
public void setTextSize(int textSize) {
this.textSize = textSize * getResources().getDisplayMetrics().density;
mRadialMenuPaint.setTextSize(this.textSize);
invalidate();
}
/**
* @return the mOpenButtonScaleFactor
*/
public int getOpenButtonScaleFactor() {
return mOpenButtonScaleFactor;
}
/**
* @param mOpenButtonScaleFactor the mOpenButtonScaleFactor to set
*/
public void setOpenButtonScaleFactor(int mOpenButtonScaleFactor) {
this.mOpenButtonScaleFactor = mOpenButtonScaleFactor;
invalidate();
}
}